package com.beiding.natago2;

import com.beiding.dbrequest.client.Client;
import com.beiding.dbrequest.client.ClientGroup;
import com.beiding.dbrequest.client.ClientStrategy;
import com.beiding.dbrequest.common.Common;
import com.beiding.dbrequest.common.Dispatcher;
import com.beiding.dbrequest.common.Intercept;
import com.beiding.dbrequest.common.Request;
import com.beiding.dbrequest.server.Server;
import com.beiding.dbrequest.utils.QueryUtils;
import com.beiding.natago2.component.ClientExecutor;
import com.beiding.natago2.component.MyChannelSelector;
import com.beiding.natago2.component.ServerExecutor;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.StringUtil;
import lombok.extern.java.Log;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * @author 丁常磊
 * @date 2021/7/26 11:35
 */

@Log
public class Starter {


    /**
     * todo 支持自定义模式启动,如果是自定义模式启动 必须指定一个启动的类名(可以是插件中的)
     *
     *
     * 增加一个plugins的目录,里面放插件的jar包
     *
     * @param args
     */

    public static void main(String[] args) {
        List<File> list = new ArrayList<>();
        for (String arg : args) {
            List<File> confFile = getConfFile(arg);
            list.addAll(confFile);
        }

        list.sort((a, b) -> {
            String ap = a.getAbsolutePath();
            String bp = b.getAbsolutePath();
            int len = ap.length() > bp.length() ? bp.length() : ap.length();
            for (int i = 0; i < len; i++) {
                char ca = ap.charAt(i);
                char cb = bp.charAt(i);
                if (cb > ca) {
                    return -1;
                } else if (cb < ca) {
                    return 1;
                }
            }
            if (ap.length() > bp.length()) {
                return 1;
            } else if (ap.length() < bp.length()) {
                return -1;
            }
            return 0;
        });

        for (File file : list) {
            Starter starter = new Starter();
            try {
                starter.handleParams(file);
                String mode = starter.get("mode");
                if ("client".equals(mode)) {
                    starter.startAsClient();
                } else if ("server".equals(mode)) {
                    starter.startAsServer();
                } else {
                    starter.error("mode=client|server  不支持其他模式");
                }
                log.info("启动成功 " + file.getPath() + " " + starter.params);
            } catch (Exception e) {
                log.warning("启动失败 " + file.getPath() + " " + starter.params);
                e.printStackTrace();
            }
        }


    }

    private static List<File> getConfFile(String t) {
        List<File> list = new ArrayList<>();
        File file = new File(t);
        if (file.exists()) {
            if (file.isDirectory()) {
                deepDir(file, list);
            } else if (file.isFile()) {
                list.add(file);
            }
        }
        return list;
    }

    private static void deepDir(File dir, List<File> list) {
        File[] files = dir.listFiles();
        if (files != null && files.length > 0) {
            for (File file : files) {
                if (file.isDirectory()) {
                    deepDir(file, list);
                } else if (file.isFile()) {
                    list.add(file);
                }
            }
        }
    }

    private Properties params;


    private void handleParams(File cf) throws IOException {

        Properties properties = new Properties();
        FileReader fileReader = new FileReader(cf);
        properties.load(fileReader);
        fileReader.close();
        params = properties;
    }

    private String mkHp(String host, String port) {
        StringBuilder builder = new StringBuilder();
        List<Integer> list = parsePorts(port);
        Iterator<Integer> iterator = list.iterator();
        if (iterator.hasNext()) {
            builder.append(host).append(":").append(iterator.next());
            while (iterator.hasNext()) {
                builder.append(",").append(host).append(":").append(iterator.next());
            }
        }
        return builder.toString();
    }

    private Object[] getHp(String t) {
        Object[] hp = new Object[2];
        if (t.contains(":")) {
            String[] split = t.split(":");
            if (split.length != 2) {
                throw new RuntimeException("不可识别的目标地址:" + t);
            }
            hp[0] = split[0];
            hp[1] = Integer.parseInt(split[1]);
        } else {
            hp[0] = t;
            hp[1] = 80;
        }
        return hp;
    }

    private void startAsClient() {

        String channelHost = get("channel-host");
        String channelPort = get("channel-port");
        String channel = get("channel");

        String channelNumber = get("channel-number");

        String targetHost = get("target-host");
        String targetPort = get("target-port");
        String target = get("target");

        if (target == null) {
            if (targetHost != null && targetPort != null) {
                target = mkHp(targetHost, targetPort);
            }
        }
        if (channel == null) {
            if (channelHost != null && channelPort != null) {
                channel = mkHp(channelHost, channelPort);
            }
        }

        notBlank(target, "target(目标)不可为空");
        notBlank(channel, "channel(内部通道)不可为空");


        String debugger = get("debugger");
        String dataParse = get("data-parse");
        String password = get("password");
        //优先级
        String priority = get("priority");

        int cn;
        if (channelNumber == null) {
            cn = Common.availableProcessors();
        } else {
            cn = Integer.parseInt(channelNumber);
        }

        Map<String, String> mapInnerPort2Target = mapA2b(channel, target);
        mapInnerPort2Target.forEach((inn, t) -> {

            Object[] hp1 = getHp(inn);

            //启动客户端
            ClientGroup clientGroup = ClientGroup.create(cn, (String) hp1[0], (Integer) hp1[1], "#");
            ClientStrategy clientStrategy = new ClientStrategy() {

                private Serializable getRegisterContent(Client client) {
                    Map<String, String> data = new HashMap<>();
                    data.put("password", password);
                    data.put("priority", priority);
                    return client.getName() + "?" + QueryUtils.toQuery(data);
                }

                @Override
                public void onActive(Client client, ChannelHandlerContext ctx) {
                    client.register(getRegisterContent(client));
                }

            };
            clientGroup.each(client -> client.setClientStrategy(clientStrategy));
            clientGroup.start().syncR();

            //客户端启动
            ClientExecutor clientExecutor = new ClientExecutor();
            if (dataParse != null) {
                clientExecutor.setDataParse(dataParse);
            }
            if ("open".equals(debugger)) {
                clientExecutor.setDebugger(true);
            }
            clientExecutor.setClientGroup(clientGroup);

            Object[] hp2 = getHp(t);

            clientExecutor.setTargetHost((String) hp2[0]);
            clientExecutor.setTargetPort((Integer) hp2[1]);

            clientExecutor.init();
        });

    }

    private void startAsServer() {

        String serverPort = get("server-port");
        String channelPort = get("channel-port");

        notBlank(serverPort, "server-port(对外服务端口)不可为空");
        notBlank(channelPort, "channel-port(内部通道端口)不可为空");

        String debugger = get("debugger");
        String dataParse = get("data-parse");

        //密码
        final String password = get("password");

        String passwordAt = get("password-at");

        Map<String, String> mapPasswordAt;

        if (passwordAt != null) {
            mapPasswordAt = mapAt(passwordAt);
        } else {
            mapPasswordAt = Collections.emptyMap();
        }

        Map<Integer, Integer> portMap = mapServer2InnerPort(serverPort, channelPort);
        portMap.forEach((sp, cp) -> {
            //启动服务端
            Server server = Server.create(cp);
            Dispatcher dispatcher = server.getDispatcher();

            String cpw = mapPasswordAt.get(cp + "");

            if (cpw == null) {
                cpw = password;
            }

            if (cpw != null) {
                String fcpw = cpw;
                dispatcher.intercept("/base/register", new Intercept() {
                    @Override
                    public boolean shouldIntercept(Request request) {
                        String body = (String) request.getBody();
                        Map<String, String> query = QueryUtils.getQuery(body);
                        String pw = query.get("password");
                        return !fcpw.equals(pw);
                    }

                    @Override
                    public Object doIntercept(Request request) {
                        throw new RuntimeException("连接失败:密码错误");
                    }

                });
            }

            server.setChannelSelector(new MyChannelSelector());

            server.start().syncR();
            //服务端启动
            ServerExecutor serverExecutor = new ServerExecutor();
            if (dataParse != null) {
                serverExecutor.setDataParse(dataParse);
            }
            if ("open".equals(debugger)) {
                serverExecutor.setDebugger(true);
            }
            serverExecutor.setServer(server);
            serverExecutor.setServerPort(sp);
            log.info(serverExecutor.start().syncR() + "");
        });
    }


    private Map<String, String> mapAt(String at) {
        Map<String, String> r = new HashMap<>();
        String[] split = at.split(",");
        for (String s : split) {
            String[] strings = s.split("@");
            if (strings.length != 2) {
                throw new IllegalArgumentException("无法解析的参数:" + at);
            }
            r.put(strings[1], strings[0]);
        }
        return r;
    }

    private Map<Integer, Integer> mapServer2InnerPort(String server, String inner) {
        List<Integer> serverPorts = parsePorts(server);
        List<Integer> innerPorts = parsePorts(inner);
        if (serverPorts.size() != innerPorts.size()) {
            throw new RuntimeException("端口号数量不匹配");
        }
        Map<Integer, Integer> r = new HashMap<>();
        for (int i = 0; i < serverPorts.size(); i++) {
            r.put(serverPorts.get(i), innerPorts.get(i));
        }
        return r;
    }

    private Map<String, String> mapA2b(String a, String b) {
        List<String> targets = parseHP(b);
        List<String> inners = parseHP(a);
        if (targets.size() != inners.size()) {
            throw new RuntimeException("地址数量不匹配");
        }
        Map<String, String> r = new HashMap<>();
        for (int i = 0; i < targets.size(); i++) {
            r.put(inners.get(i), targets.get(i));
        }
        return r;
    }

    private List<String> parseHP(String text) {
        if (text.contains(",")) {
            String[] split = text.split(",");
            return Arrays.asList(split);
        } else {
            return Collections.singletonList(text);
        }
    }

    private List<Integer> parsePorts(String text) {
        if (text.contains("*")) {
            List<Integer> list = new ArrayList<>();
            String[] split = text.split("\\*");
            if (split.length != 2) {
                throw new IllegalArgumentException("无效参数:" + text);
            }
            int from = Integer.parseInt(split[0]);
            int size = Integer.parseInt(split[1]);
            for (int i = 0; i < size; i++) {
                list.add(from + i);
            }
            return list;
        } else if (text.contains(",")) {
            List<Integer> list = new ArrayList<>();
            String[] split = text.split(",");
            for (String s : split) {
                list.add(Integer.parseInt(s));
            }
            return list;
        } else {
            return Collections.singletonList(Integer.parseInt(text));
        }
    }

    private String get(String key) {
        return params.getProperty(key);
    }

    private void error(String msg) {
        throw new RuntimeException(msg);
    }

    public void notBlank(String obj, String msg) {
        if (StringUtil.isNullOrEmpty(obj)) {
            error(msg);
        }
    }

}
